package edu.gmu.streams;

import java.io.*;

/**
 * LittleEndianInputStream is a DataInput.  Borrowed liberally from
 * Elliot Rusty Harold's Java IO Book.
 * @author  James H. Pope
 * @version $Revision:$ $Date:$
 * @see     java.io.DataInputStream
 */

public class LittleEndianInputStream extends FilterInputStream
  implements DataInput
{

  /**
   * Creates a new Little-Endian input stream and chains it to the input
   * stream specified by the in argument.
   *
   * @param  in the underlying input stream.
   * @see    java.io.InputStream#in
   */
  public LittleEndianInputStream(InputStream in)
  {
    super(in);
  }


  /**
   * Reads a <code>boolean<\code> from the underlying input stream by
   * reading a single byte. If the byte is zero, false is returned. If
   * the byte is positive, true is returned.
   *
   * @return    the <code>boolean</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public boolean readBoolean() throws IOException
  {
    int bool = in.read();
    if(bool == -1) throw new EOFException();
    return (bool != 0);
  }


  /**
   * Reads a <code>byte<\code> from the underlying input stream with
   * a value between -128 and 127.
   *
   * @return    the <code>byte</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public byte readByte() throws IOException
  {
    int temp = in.read();
    if(temp == -1) throw new EOFException();
    return (byte)temp;
  }

  /**
   * Reads an unsigned <code>byte<\code> from the underlying input stream with
   * a value between 0 and 255.
   *
   * @return    the <code>byte</code> value read (as an int).
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public int readUnsignedByte() throws IOException
  {
    int temp = in.read();
    if(temp == -1) throw new EOFException();
    return temp;
  }


  /**
   * Reads a two byte signed <code>short<\code> from the underlying input stream
   * in little-endian order, low byte first.
   *
   * @return    the <code>short</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public short readShort() throws IOException
  {
    int byte1 = in.read();
    int byte2 = in.read();
    // Really only need to test last byte
    // If byte1 = -1 so will byte2
    // We check each one anyway, the short curcuit helps performance
    if(byte2 == -1 || byte1 == -1) throw new EOFException();
    return (short)((byte2 << 8) + byte1);
  }


  /**
   * Reads a two byte unsigned <code>short<\code> from the underlying
   * input stream in little-endian order, low byte first.
   *
   * @return    the <code>int</code> unsigned short read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public int readUnsignedShort() throws IOException
  {
    int byte1 = in.read();
    int byte2 = in.read();
    if(byte2 == -1 || byte1 == -1) throw new EOFException();
    return (byte2 << 8) + byte1;
  }


  /**
   * Reads a two byte Unicode <code>char<\code> from the underlying
   * input stream in little-endian order, low byte first.
   *
   * @return    the <code>char</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public char readChar() throws IOException
  {
    int byte1 = in.read();
    int byte2 = in.read();
    if(byte2 == -1 || byte1 == -1) throw new EOFException();
    return (char)((byte2 << 8) + byte1);
  }


  /**
   * Reads a four byte signed <code>int<\code> from the underlying
   * input stream in little-endian order, low byte first.
   *
   * @return    the <code>int</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public int readInt() throws IOException
  {
    int byte1, byte2, byte3, byte4;
    synchronized(this)
    {
      byte1 = in.read();
      byte2 = in.read();
      byte3 = in.read();
      byte4 = in.read();
    }
    if( (byte4 == -1) || (byte3 == -1) || (byte2 == -1) || (byte1 == -1) )
    {
      throw new EOFException();
    }
    return ((byte4 << 24) + (byte3 << 16) + (byte2 << 8) + byte1);
  }


  /**
   * Reads a eight byte signed <code>long<\code> from the underlying
   * input stream in little-endian order, low byte first.
   *
   * @return    the <code>long</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public long readLong() throws IOException
  {
    long byte1, byte2, byte3, byte4, byte5, byte6, byte7, byte8;
    synchronized(this)
    {
      byte1 = in.read();
      byte2 = in.read();
      byte3 = in.read();
      byte4 = in.read();
      byte5 = in.read();
      byte6 = in.read();
      byte7 = in.read();
      byte8 = in.read();
    }
    if( (byte8 == -1) || (byte7 == -1) || (byte6 == -1) || (byte5 == -1) ||
        (byte4 == -1) || (byte3 == -1) || (byte2 == -1) || (byte1 == -1) )
    {
      throw new EOFException();
    }
    return ((byte8 << 56) + (byte7 << 48) + (byte6 << 40) + (byte5 << 32) +
            (byte4 << 24) + (byte3 << 16) + (byte2 <<  8) +  byte1);
  }


  /**
   * Reads a string of no more than 65,535 characters from the underlying
   * input stream using little endian UTF-8 encoding. This method first
   * reads a two byte short in little-endian order as if by the
   * <code>readShort</code> method. This gives the number of bytes in the
   * UTF-8 encoded version of the string. Next this many bytes are read
   * and decoded as little-endian UTF-8 encoded characters.
   *
   * @return    the decoded String.
   * @exception UTFDataFormatException if the string cannot be decoded.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public String readUTF() throws IOException
  {
    // First two should be number of bytes (not to include these two).
    // Note that this only allows 2 to the 16 = 65536 bytes.
      
    /*
     * Note that we write the length in big-endian.  The UTF-8
     * standard is a byte-for-byte standard (essentially big-endian),
     * thus a "big-endian utf-8 string" and a "little-endian utf-8 string"
     * are exactly the same.
     */
    int byte1 = in.read();
    int byte2 = in.read();
    
    //int numbytes = readUnsignedShort();
    int numbytes = (byte1 << 8) + byte2;
    System.out.println( "Length = " + numbytes );
    
    char result[] = new char[numbytes];
    int numread = 0;
    int numchars = 0;

    while(numread < numbytes)
    {
      int c1 = readUnsignedByte();
      int c2, c3;

      int test = c1 >> 4;
      // If byte = 0XXX XXXX >> 4 = 0111 < 8
      if(test < 8) //one byte
      {
        numread ++;
        result[numchars++] = (char)c1;
      }
      // If byte = 110X XXXX >> 4 = 110X either = 12 or = 13
      else if(test == 12 || test == 13) //two bytes
      {
        numread += 2;
        if(numread > numbytes) throw new UTFDataFormatException();
        c2 = readUnsignedByte();
        // Make sure byte c2 = 10XXXXXX & 11000000 = 10000000
        if((c2 & 0xC0) != 0x80) throw new UTFDataFormatException();
        // Make a char again of 11 bits long -  110XXXXX      10XXXXXX
        // Remove header bits              c1 & 00011111 c2 & 00111111
        // Shift and "OR" back together  c1 00000XXX XX000000 (<<6)
        //                               c2 00000000 00XXXXXX
        // char result                      00000XXX XXXXXXXX
        result[numchars++] = (char)(((c1 & 0x1F) << 6) | (c2 & 0x3F));
      }
      // If byte = 1110 XXXX >> 4 = 1110 = 14
      else if(test == 14) //three bytes
      {
        numread += 3;
        if(numread > numbytes) throw new UTFDataFormatException();
        c2 = readUnsignedByte();
        c3 = readUnsignedByte();
        // Make sure both c2 and c3 have appropriate headers
        // byte c2 = 10XXXXXX & 11000000 = 10000000
        // byte c3 = 10XXXXXX & 11000000 = 10000000
        if(((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80))
        {
          throw new UTFDataFormatException();
        }
        // Make a char again of 16 bits long - 1110XXXX    10XXXXXX    10XXXXXX
        // Remove header bits              c1 &00001111 c2&00111111 c3&00111111
        // Shift and "OR" back together c1 XXXX0000 00000000 (<<12)
        //                              c2 0000XXXX XX000000 (<<6)
        //                              c3 00000000 00XXXXXX
        // char result                     XXXXXXXX XXXXXXXX
        result[numchars++] = (char)(((c1 & 0x0F) << 12) |
                                    ((c2 & 0x3F) << 6) |
                                     (c3 & 0x3F)  );
      }
      else //we have a problem
      {
        throw new UTFDataFormatException();
      }
    } //end while
    return new String(result, 0, numchars);
  }


  /**
   * Reads the next eight bytes as a <code>double<\code> from the underlying
   * input stream in little-endian order, low byte first.
   *
   * @return    the <code>double</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public double readDouble() throws IOException
  {
    return Double.longBitsToDouble(this.readLong());
  }

  /**
   * Reads the next four bytes as a <code>float<\code> from the underlying
   * input stream in little-endian order, low byte first.
   *
   * @return    the <code>float</code> value read.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public float readFloat() throws IOException
  {
    return Float.intBitsToFloat(this.readInt());
  }


  /**
   * Skips exactly <code>n</code> bytes of input in the underlying
   * input stream. This method blocks until all the bytes are skipped,
   * the end of the stream is detected, or an exception is thrown.
   *
   * @param     n the number of bytes to skip.
   * @return    the number of bytes skipped, generally n.
   * @exception EOFException if the end of the underlying input stream has
   *            been reached before skipping the n bytes.
   * @exception IOException  if the underlying stream throws an exception.
   */
  public int skipBytes(int n) throws IOException
  {
    int i = 0;
    for(i = 0; i < n; i += (int) skip(n - i));
    return i;
  }

  // The following methods are provided for the DataInput interface

  /**
   * This method always throws IOException. It is only provided to implement the
   * DataInput interface.
   * See the general contract of the <code>readLine</code>
   * method of <code>DataInput</code>.
   * <p>
   * Bytes
   * for this operation are read from the contained
   * input stream.
   *
   * @deprecated This method does not properly convert bytes to characters.
   * As of JDK&nbsp;1.1, the preferred way to read lines of text is via the
   * <code>BufferedReader.readLine()</code> method.  Programs that use the
   * <code>DataInputStream</code> class to read lines can be converted to use
   * the <code>BufferedReader</code> class by replacing code of the form:
   * <blockquote><pre>
   *     DataInputStream d =&nbsp;new&nbsp;DataInputStream(in);
   * </pre></blockquote>
   * with:
   * <blockquote><pre>
   *     BufferedReader d
   *          =&nbsp;new&nbsp;BufferedReader(new&nbsp;InputStreamReader(in));
   * </pre></blockquote>
   *
   * @return     null
   * @exception  IOException  if an I/O error occurs.
   * @see        java.io.BufferedReader#readLine()
   * @see        java.io.FilterInputStream#in
   */
  public final String readLine() throws IOException
  {
    throw new IOException("Method not supported.");
  } //end readLine()

  /**
   * See the general contract of the <code>readFully</code>
   * method of <code>DataInput</code>.
   * <p>
   * Bytes for this operation are read from the contained
   * input stream. The order of the bytes are not changed.
   *
   * @param      b   the buffer into which the data is read.
   * @exception  EOFException  if this input stream reaches the end before
   *               reading all the bytes.
   * @exception  IOException   if an I/O error occurs.
   * @see        java.io.FilterInputStream#in
   */
  public final void readFully(byte b[]) throws IOException
  {
    readFully(b, 0, b.length);
  }

  /**
   * See the general contract of the <code>readFully</code>
   * method of <code>DataInput</code>.
   * <p>
   * Bytes for this operation are read from the contained
   * input stream. The order of the bytes are not changed.
   *
   * @param      b     the buffer into which the data is read.
   * @param      off   the start offset of the data.
   * @param      len   the number of bytes to read.
   * @exception  EOFException  if this input stream reaches the end before
   *               reading all the bytes.
   * @exception  IOException   if an I/O error occurs.
   * @see        java.io.FilterInputStream#in
   */
  public final void readFully(byte b[], int off, int len) throws IOException
  {
    InputStream in = this.in;
    int n = 0;
    while (n < len)
    {
      int count = in.read(b, off + n, len - n);
      if (count < 0)
      {
        throw new EOFException();
      }
      n += count;
    }
  } //end readFully


} //end LittleEndianInputStream

